package redisclient

import (
	"context"
	"crypto/aes"
	"crypto/cipher"
	"fmt"
	"gitee.com/lsy007/redisclient/model"
	"github.com/golang/protobuf/proto"
	"github.com/golang/protobuf/ptypes"
	"github.com/smallnest/rpcx/client"
	"github.com/smallnest/rpcx/protocol"
	"go.uber.org/zap"
	"time"
)

var response Response

type Response struct {
	Int             int64
	IntSlice        []int64
	Float           float64
	String          string
	StringSlice     []string
	Bool            bool
	BoolSlice       []bool
	StringStringMap map[string]string
	StringIntMap    map[string]int64
	Time            time.Time
	Bytes           []byte
}

func (c *RedisClient) Request(fun string, redisType string, pbMessage *model.RedisArgsModel) (response Response, err error) {
	//defer loger.Debug("Redis Request", zap.Any("redisClient", c),
	//	zap.Any("model", pbMessage), zap.String("fun", fun), zap.Any("response", &response), zap.Error(err))
	// 1. 获取加密的 Redis 操作数据
	var bytes []byte
	if pbMessage != nil {
		// 将 Redis 操作参数进行 protobuf 编码
		bytes, err = proto.Marshal(pbMessage)
		if err != nil {
			return
		}
		// 判断是否开启 AES 加密，如果开启， Redis 操作数据执行 AES 加密
		if c.Service.Encrypt == "on" {
			if len(c.RequestId) < 16 {
				err = fmt.Errorf("the length of the requestId must be greater than 16")
				return
			}
			iv := []byte(c.RequestId[0:aes.BlockSize])
			if bytes, err = aesCTREncrypt(bytes, []byte(c.Service.Secret), iv); err != nil {
				return
			}
		}
	}
	// 2. 组装请求参数
	if c.Region == "" {
		c.Region = c.Header.Region
	}
	args := model.Args{Type: redisType, Func: fun, Model: bytes,
		Header: &model.Header{
			RequestId: c.RequestId,
			BrandCode: c.Header.BrandCode,
			Region:    c.Region,
			Server:    c.Header.Server,
		},
	}
	if c.Service.Encrypt == "on" {
		args.Header.Encrypt = 1
	}
	// 3.判断是否为请求所有区域
	if c.Region == "all" {
		return c.GetAllRegionReply(&args)
	}
	// 4.执行 rpc 请求
	var reply model.Reply
	if err = SendRequest(c.Service.Addr, c.Service.RpcRegisterName, redisType+"Request", &args, &reply); err != nil {
		c.Error(redisType+" request error", zap.Error(err),
			zap.Any("addr", c.Service.Addr), zap.String("register_name", c.Service.RpcRegisterName), zap.Any("args", args))
		return
	}
	// 5. 判断请求结果
	if reply.ErrMsg != "" {
		err = fmt.Errorf(reply.ErrMsg)
		return
	}
	// 6. 判断响应结果
	if reply.Reply == nil {
		return
	}
	return GetRegionResponse(reply.Reply)
}

func (c *RedisClient) GetAllRegionReply(args interface{}) (response Response, err error) {
	// 1.执行 rpc 请求
	var allReply model.AllRegionReply
	if err = SendRequest(c.Service.Addr, c.Service.RpcRegisterName, "AllRegionRequest", args, &allReply); err != nil {
		c.Error("AllRegionRequest error", zap.Error(err),
			zap.Any("addr", c.Service.Addr), zap.String("register_name", c.Service.RpcRegisterName), zap.Any("args", args))
		return
	}
	// 2. 遍历响应结果
	allRegionReply := make(map[string]Response, 0)
	for _, v := range allReply.Reply {
		if v.ErrMsg != "" {
			return Response{}, fmt.Errorf("%s error：%s", v.Region, v.ErrMsg)
		}
		if v.Reply == nil {
			allRegionReply[v.Region] = Response{}
			continue
		}
		if allRegionReply[v.Region], err = GetRegionResponse(v.Reply); err != nil {
			return
		}
		response = allRegionReply[v.Region]
	}
	c.AllResponse = allRegionReply
	return
}

func GetRegionResponse(reply []byte) (res Response, err error) {
	// 1. 解析响应结果
	var response model.RedisResponseModel
	if err = proto.Unmarshal(reply, &response); err != nil {
		return
	}
	// 2. 返回结果
	res = Response{
		Int:             response.Int,
		IntSlice:        response.IntSlice,
		Float:           response.Float,
		String:          response.String_,
		StringSlice:     response.StringSlice,
		Bool:            response.Bool,
		BoolSlice:       response.BoolSlice,
		StringStringMap: response.StringStringMap,
		StringIntMap:    response.StringIntMap,
		Bytes:           response.Bytes,
	}
	if response.Time != nil {
		res.Time, err = ptypes.Timestamp(response.Time)
	}
	return
}

func SendRequest(addr, registerName, method string, args interface{}, reply interface{}) (err error) {
	//loger.Debug("send request", zap.Any("server", server), zap.Any("args", args), zap.Any("reply", reply))
	c := client.NewPeer2PeerDiscovery(addr, "")
	option := client.DefaultOption
	option.SerializeType = protocol.ProtoBuffer // 设置编解码格式为 protobuf 格式
	rpcClient := client.NewXClient(registerName, client.Failtry, client.RandomSelect, c, option)
	defer rpcClient.Close()
	return rpcClient.Call(context.Background(), method, args, reply)
}

// 基于aes加密算法的ctr分组模式的加密
// key加解密秘钥，长度32字节；iv用proId截取长度16字节的值
func aesCTREncrypt(plainText, key, iv []byte) ([]byte, error) {
	// 创建des算法的接口
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	// 创建CTR分组模式下的接口
	stream := cipher.NewCTR(block, iv)
	// 使用XORKeyStream完成加密,可以创建dst参数，也可不创建，若要创建注意与填充后的明文等长
	cipherText := make([]byte, len(plainText))
	stream.XORKeyStream(cipherText, plainText)
	// 返回密文
	return cipherText, nil
}
